package yagwl.client.widget.internal;

import java.util.List;
import java.util.logging.Logger;

import yagwl.client.model.ItemListCache;
import yagwl.client.model.DataReadyEventBus;
import yagwl.client.model.ItemDTO;
import yagwl.client.model.ItemListEvent;
import yagwl.client.model.ItemListEvent.ItemListEventHandler;
import yagwl.client.resource.Resources;
import yagwl.client.widget.Spacer;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

/**
 * Implements pagination (previous page, next page) for a list of objects.
 * Classes extending AbstractPager will actually display the objects.
 * Responsible for displaying sort options and sorting events. Responsible for
 * setting display range.
 * <p>
 * List of objects are fetched from server in programmable fetch sizes. Cache
 * Manager caches the objects in memory. Once pagination reaches end of fetched
 * objects, AbstractPager asks the cache manager to fetch more objects. Cache
 * manager fetches more objects and raises ItemListEvent, which is then caught
 * by AbstractPager and next page is displayed.
 */
public abstract class AbstractPager extends Composite implements
    ItemListEventHandler {
  private static final Logger log = Logger.getLogger(AbstractPager.class
      .getName());
  // container widget variables
  private static final int TopNavRow = 0;
  private static final int DataPanelRow = 1;
  private static final int WaitingRow = 2;
  private static final int BotNavRow = 3;
  private final Grid grid = new Grid(BotNavRow + 1, 1);
  // top bar variables
  private final PageSizer pageSizer = new PageSizer();
  private ButtonBar buttonBar;
  private Sorter sorter;
  // page navigation variables
  protected int pageStartIndex = 0;
  private final Label prevL[] = new Label[2];
  private final Label nextL[] = new Label[2];
  private final Label pageSizeL[] = new Label[2];
  // data
  private final ItemListCache listCache;

  /**
   * Sets the cache manager that will manage the list of objects
   */
  public AbstractPager(ItemListCache listCache) {
    this.listCache = listCache;
    History.addValueChangeHandler(new PaginationValueChangeHandler());
    // register self as a listener of data ready events (this was in DataTable)
    DataReadyEventBus.getInstance().addHandler(ItemListEvent.TYPE, this);
    initWidget(grid);
  }

  private class PaginationValueChangeHandler implements
      ValueChangeHandler<String> {

    @Override
    public void onValueChange(ValueChangeEvent<String> event) {
      if (listCache.getHistoryToken() == null) {
        return;
      }
      String historyToken = event.getValue();
      log.info("PaginationValueChangeHandler:historyToken: " + historyToken);
      if (!historyToken.startsWith(listCache.getHistoryToken()))
        return;
      String[] tokens = historyToken.split("/");
      if (tokens.length < 2)
        pageStartIndex = 0;
      else {
        String startIndexPart = tokens[1];
        try {
          pageStartIndex = Integer.parseInt(startIndexPart);
        } catch (NumberFormatException e) {
          pageStartIndex = 0;
        }
      }
      populate();
    }

  }

  protected void renderUI() {
    log.info("renderUI: Entering");
    grid.setWidget(TopNavRow, 0, getActionAndNavBar(0));
    grid.setWidget(BotNavRow, 0, getActionAndNavBar(1));
    grid.getRowFormatter().setVisible(TopNavRow, false);
    grid.getRowFormatter().setVisible(BotNavRow, false);
    grid.setWidget(DataPanelRow, 0, getDataPanel());
    grid.setWidget(WaitingRow, 0, getWaitingWidget());
    grid.getCellFormatter().setHeight(WaitingRow, 0, "100px");
    grid.getCellFormatter().setAlignment(WaitingRow, 0,
        HasHorizontalAlignment.ALIGN_CENTER, HasVerticalAlignment.ALIGN_MIDDLE);
    grid.setWidth("100%");
    if (sorter != null)
      listCache.setSortOrderNumber(sorter.getSortOrderNumber());
    populate();
  }

  private Widget getActionAndNavBar(int i) {
    HorizontalPanel actionNavHP = new HorizontalPanel();
    HorizontalPanel navHP = new HorizontalPanel();
    actionNavHP.setWidth("100%");

    if (i == 0 && buttonBar != null) {
      actionNavHP.add(buttonBar);
      buttonBar.renderUI();
    }

    if (i == 0 && sorter != null && sorter.isRenderable()) {
      sorter.renderUI(new Sorter.SortOrderChangeHandler() {
        @Override
        public void onChange() {
          listCache.setSortOrderNumber(sorter.getSortOrderNumber());
          pageStartIndex = 0;
          populate();
        }
      });
      navHP.add(sorter);
      navHP.setCellVerticalAlignment(sorter, HasVerticalAlignment.ALIGN_MIDDLE);
      navHP.add(new Spacer(20, 1));
    }

    if (i == 0 && pageSizer != null && pageSizer.isRenderable()) {
      pageSizer.renderUI(new PageSizer.PageSizeChangeHandler() {
        @Override
        public void onChange() {
          populate();
        }
      });
      navHP.add(pageSizer);
      navHP.setCellVerticalAlignment(pageSizer,
          HasVerticalAlignment.ALIGN_MIDDLE);
      navHP.add(new Spacer(20, 1));
    }

    Widget navBar = getNavigationBar(i);
    navHP.add(navBar);
    navHP.setCellVerticalAlignment(navBar, HasVerticalAlignment.ALIGN_MIDDLE);

    actionNavHP.add(navHP);
    actionNavHP.setCellHorizontalAlignment(navHP,
        HasHorizontalAlignment.ALIGN_RIGHT);
    actionNavHP.setCellVerticalAlignment(navHP,
        HasVerticalAlignment.ALIGN_MIDDLE);

    return actionNavHP;
  }

  private Widget getNavigationBar(int i) {
    HorizontalPanel navHP = new HorizontalPanel();

    prevL[i] = new Label("Previous");
    nextL[i] = new Label("Next");
    pageSizeL[i] = new Label();
    pageSizeL[i].setWordWrap(false);
    navHP.add(prevL[i]);
    navHP.add(new Spacer(10, 1));
    navHP.add(pageSizeL[i]);
    navHP.add(new Spacer(10, 1));
    navHP.add(nextL[i]);
    navHP.setCellVerticalAlignment(prevL[i], HasVerticalAlignment.ALIGN_MIDDLE);
    navHP.setCellVerticalAlignment(nextL[i], HasVerticalAlignment.ALIGN_MIDDLE);
    navHP.setCellVerticalAlignment(pageSizeL[i],
        HasVerticalAlignment.ALIGN_MIDDLE);

    final int ii = i;
    prevL[i].addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent event) {
        navigate(false);
        if (ii == 1)
          scrollToTop();
      }
    });
    nextL[i].addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent event) {
        navigate(true);
        if (ii == 1)
          scrollToTop();
      }
    });

    return navHP;
  }

  private static native void scrollToTop() /*-{
    $wnd.scroll(0, 0);
  }-*/;

  private Widget getWaitingWidget() {
    Image i = new Image(Resources.I.thumbProgress());
    return i;
  }

  protected abstract Widget getDataPanel();

  /*
   * navigation and data population methods from here **************************
   */

  @Override
  public void onItemListEvent(ItemListEvent event) {
    log.info("onDataReady: Entering");
    if (event.getListCache() == null)
      return;
    String eventName = event.getListCache().getClass().getName();
    String objName = listCache.getClass().getName();
    if (objName != eventName)
      return;
    log.info("onDataReady: ItemListEvent caught: " + eventName);
    populate();
  }

  private void populate() {
    log.info("populate: Entering");
    // check if data to be displayed is already loaded
    List<?> objectList = listCache.getList();
    if (!listCache.isFullyLoaded()
        && (objectList.size() < pageStartIndex + getPageSize())) {
      grid.getRowFormatter().setVisible(WaitingRow, true);
      grid.getRowFormatter().setVisible(DataPanelRow, false);
      // load next set of data; after loading,
      // cache manager will raise ItemListEvent, which will be caught
      // by onDataReady() and the data panel will be populated
      log.info("populate: not enough data; calling listCache.fetchNext()");
      listCache.fetchNext();
      return;
    }
    populateDataPanel();
    populatePageNavigation();
    grid.getRowFormatter().setVisible(WaitingRow, false);
    grid.getRowFormatter().setVisible(DataPanelRow, true);
  }

  protected abstract void populateDataPanel();

  protected void populatePageNavigation() {
    grid.getRowFormatter().setVisible(TopNavRow, true);
    grid.getRowFormatter().setVisible(BotNavRow, true);
    StringBuffer sbuf = new StringBuffer();
    sbuf.append(listCache.getList().isEmpty() ? 0 : pageStartIndex + 1);
    sbuf.append(" - ");
    sbuf.append(isLastPage() ? listCache.getList().size() : pageStartIndex
        + getPageSize());
    if (listCache.isFullyLoaded()) {
      sbuf.append(" of " + listCache.getList().size());
    }
    for (int i = 0; i < 2; i++) {
      prevL[i].setStyleName(isFirstPage() ? "ya-link-1-disabled" : "ya-link-1");
      nextL[i].setStyleName(isLastPage() ? "ya-link-1-disabled" : "ya-link-1");
      pageSizeL[i].setText(sbuf.toString());
    }
  }

  private void navigate(boolean nextClick) {
    if (nextClick) {
      if (isLastPage())
        return;
      pageStartIndex += getPageSize();
    } else {
      if (isFirstPage())
        return;
      pageStartIndex -= getPageSize();
      if (pageStartIndex < 0)
        pageStartIndex = 0;
    }
    // the following will add history item and also
    // cause ValueChangeHandler event to be raised
    // which will call populate()
    History.newItem(listCache.getHistoryToken() + "/" + pageStartIndex);
    // populate();
  }

  private boolean isFirstPage() {
    return pageStartIndex == 0;
  }

  private boolean isLastPage() {
    if (!listCache.isFullyLoaded())
      return false;
    return pageStartIndex + getPageSize() >= listCache.getList().size();
  }

  /**
   * Adds button
   */
  public void addButton(String label, ClickHandler clickHandler) {
    Button button = new Button(label);
    button.addClickHandler(clickHandler);
    if (buttonBar == null)
      buttonBar = new ButtonBar();
    buttonBar.add(button);
  }

  /**
   * Displays or un-displays busy indicator
   */
  public void setBusy(boolean busy) {
    buttonBar.setBusy(busy);
  }

  /**
   * Displays message for 4 seconds
   * 
   * @param message
   *          message to be displayed
   */
  public void setMessage(String message) {
    buttonBar.setMessage(message, 4);
  }

  /**
   * Displays message
   * 
   * @param message
   *          message to be displayed
   * @param displaySeconds
   *          seconds for how long message is displayed
   */
  public void setMessage(String message, int displaySeconds) {
    buttonBar.setMessage(message, displaySeconds);
  }

  /**
   * Displays error message for 8 seconds
   * 
   * @param message
   *          error message to be displayed
   */
  public void setErrorMessage(String message) {
    buttonBar.setErrorMessage(message, 8);
  }

  /**
   * Displays error message
   * 
   * @param message
   *          error message to be displayed
   * @param displaySeconds
   *          seconds for how long error message is displayed
   */
  public void setErrorMessage(String message, int displaySeconds) {
    buttonBar.setErrorMessage(message, displaySeconds);
  }

  /**
   * Add a sort order option
   * 
   * @param number
   *          sort order number defined at the server
   * @param label
   *          label that appears on list box
   */
  public void addSortOrderOption(int number, String label) {
    if (sorter == null)
      sorter = new Sorter();
    sorter.addSortOrderOption(number, label);
  }

  public void setSortOrderOptionIndex(int sortOrderOptionIndex) {
    sorter.setSortOrderOptionIndex(sortOrderOptionIndex);
  }

  /**
   * Sets page size options to be displayed
   */
  public void setPageSizeOptions(int[] pageSizeOptions) {
    if (pageSizeOptions == null || pageSizeOptions.length == 0)
      return;
    pageSizer.setPageSizeOptions(pageSizeOptions);
  }

  /**
   * Sets which page size is to be used from the list of options
   */
  public void setPageSizeOptionIndex(int pageSizeOptionIndex) {
    pageSizer.setPageSizeOptionIndex(pageSizeOptionIndex);
  }

  protected int getPageSize() {
    return pageSizer.getPageSize();
  }

  protected ItemListCache getListCache() {
    return listCache;
  }

  protected ItemDTO getListItem(int objIdx) {
    return (ItemDTO) listCache.getList().get(objIdx);
  }

  protected int getListSize() {
    return listCache.getList().size();
  }

  protected void deleteListItem(int objIdx) {
    listCache.deleteObject(objIdx);
  }

}
